// -----------------------------------------------------------------------
//  <copyright file="AuthorizationCache.cs" company="LiuliuSoft">
//      Copyright (c) 2022-2025 DaprPlus. All rights reserved.
//  </copyright>
//  <site>https://dapr.plus</site>
//  <last-editor>郭明锋</last-editor>
//  <last-date>2025/1/6 23:05</last-date>
// -----------------------------------------------------------------------

using DaprPlus.Authorization.Domain.Entities;
using DaprPlus.Authorization.Domain.Specs;
using DaprPlus.Authorization.Properties;
using DaprPlus.Builder;
using DaprPlus.Exceptions;

using Microsoft.Extensions.Caching.Distributed;

using OSharp.Caching;


namespace DaprPlus.Authorization;

/// <summary>
/// 权限缓存，缓存项使用 资源Id 作为键，值为角色Id-操作集合的字典为值
/// </summary>
public class AuthorizationCache : IAuthorizationCache
{
    private readonly IServiceProvider _provider;
    private readonly IDistributedCache _cache;
    private readonly ILogger _logger;
    private readonly string _serviceName;
    private readonly Random _random = new();

    public AuthorizationCache(IServiceProvider provider)
    {
        _provider = provider;
        _cache = provider.GetRequiredService<IDistributedCache>();
        _logger = provider.GetLogger(GetType());
        _serviceName = provider.GetRequiredService<DaprPlusContext>().ServiceName;
    }

    /// <summary>
    /// 创建资源权限缓存
    /// </summary>
    /// <returns></returns>
    public Task BuildResourceAuthorizationCache()
    {
        return _provider.ExecuteScopedWorkAsync(async scopedProvider =>
        {
            var resourceRepository = scopedProvider.GetRepository<Resource>();
            var resources = await resourceRepository.GetAllAsync(new TrueSpecification<Resource>());
            var toSets = resources.Where(m => !m.IsLocked).ToList();
            foreach (var resource in toSets)
            {
                _ = await GetOrBuildResourceAuthorizationCache(resource, forceRefresh: true, scopedProvider);
            }
            _logger.LogInformation(ResAuthorization.Format_Auth_Resource_CreateCache_Count, toSets.Count);

            var toRemoves = resources.Except(toSets).ToList();
            if (toRemoves.Count > 0)
            {
                foreach (var resource in toRemoves)
                {
                    var key = GetResourceRolesKey(resource.Id, resource.ServiceName);
                    await _cache.RemoveAsync(key);
                }
                _logger.LogInformation(ResAuthorization.Format_Auth_Resource_RemoveCache_Count, toRemoves.Count);
            }
        });
    }

    /// <summary>
    /// 获取或创建指定资源的权限缓存，资源Id为键，角色及可执行操作的字典为值
    /// </summary>
    /// <param name="resource">资源信息</param>
    /// <param name="forceRefresh">是否强制重建</param>
    /// <param name="scopedProvider">作用域服务提供者</param>
    /// <returns>角色编号-可执行资源操作的字典</returns>
    public async Task<ResourceAuthCacheItem[]> GetOrBuildResourceAuthorizationCache(IResource resource,
        bool forceRefresh = false, IServiceProvider? scopedProvider = null)
    {
        IServiceScope? scope = null;
        try
        {
            var key = GetResourceRolesKey(resource.Id, _serviceName);
            ResourceAuthCacheItem[] items;
            if (!forceRefresh)
            {
                items = await _cache.GetAsync<ResourceAuthCacheItem[]>(key);
                if (items != null)
                {
                    _logger.LogDebug(ResAuthorization.Format_Auth_Resource_GetCache_Count, resource.Name, items.Length);
                    return items;
                }
            }

            var provider = scopedProvider;
            if (provider == null)
            {
                scope = _provider.CreateScope();
                provider = scope.ServiceProvider;
            }

            var repository = provider.GetQueryRepository<RoleResource>();
            var spec = new RoleResourceByResourceIdSpec(resource.Id).And(new RoleResourceUnlockedSpec());
            var roleResources = await repository.GetAllAsync(spec);

            // 有效期为 7 ± 1 天
            var seconds = 7 * 24 * 3600 + _random.Next(-24 * 3600, 24 * 3600);
            items = roleResources.Select(m => new ResourceAuthCacheItem
            {
                RoleId = m.RoleId,
                Operations = m.GetOperations(),
                FilterGroups = m.GetFilterGroups(),  // 现在返回 CRUD 维度的过滤条件
            }).ToArray();
            await _cache.SetAsync(key, items, seconds);
            _logger.LogDebug(ResAuthorization.Format_Auth_Resource_AddRoleCache_Count, resource.Name, items.Length);
            return items;
        }
        finally
        {
            scope?.Dispose();
        }
    }

    /// <summary>
    /// 移除指定资源的权限缓存
    /// </summary>
    /// <param name="resourceIds">资源编号</param>
    /// <returns></returns>
    public async Task RemoveResourceAuthorizationCache(params long[] resourceIds)
    {
        foreach (var resourceId in resourceIds)
        {
            var key = GetResourceRolesKey(resourceId, _serviceName);
            await _cache.RemoveAsync(key);
            _logger.LogDebug(ResAuthorization.Format_Auth_Resource_RemoveCache, resourceId);
        }
        _logger.LogInformation(ResAuthorization.Format_Auth_Resource_RemoveAuth_Count, resourceIds.Length);
    }

    /// <summary>
    /// 创建资源缓存
    /// </summary>
    /// <returns></returns>
    public Task BuildResourceCache()
    {
        return _provider.ExecuteScopedWorkAsync(async scopedProvider =>
        {
            var repository = scopedProvider.GetRepository<Resource>();
            var spec = new ResourceUnlockedSpec();
            var resources = await repository.GetAllAsync(spec);
            foreach (var resource in resources)
            {
                var key = GetResourceKey(resource.Code, _serviceName, resource.Type);
                await _cache.SetAsync(key, resource);
            }
            _logger.LogInformation(ResAuthorization.Format_Auth_Resource_AddCache_Count, resources.Count);
        });
    }

    /// <summary>
    /// 获取资源缓存
    /// </summary>
    /// <param name="code">资源码</param>
    /// <param name="type">资源类型</param>
    /// <param name="scopedProvider">区域服务提供者</param>
    /// <returns></returns>
    public async Task<IResource?> GetResourceCache(string code, ResourceType type, IServiceProvider? scopedProvider = null)
    {
        IServiceScope? scope = null;
        try
        {
            var provider = scopedProvider;
            if (provider == null)
            {
                scope = _provider.CreateScope();
                provider = scope.ServiceProvider;
            }

            var key = GetResourceKey(code, _serviceName, type);
            var resource = await _cache.GetAsync<Resource>(key);
            if (resource != null)
            {
                return resource;
            }
            var repository = provider.GetRequiredService<IQueryRepository<Resource>>();
            var spec = new ResourceByCodeAndServiceNameAndTypeSpec(code, _serviceName, type);
            resource = await repository.GetAsync(spec);
            if (resource != null)
            {
                await _cache.SetAsync(key, resource);
            }

            return resource;
        }
        finally
        {
            scope?.Dispose();
        }
    }

    /// <summary>
    /// 移除资源缓存
    /// </summary>
    /// <param name="code">资源码</param>
    /// <param name="type">资源类型</param>
    /// <returns></returns>
    public async Task<bool> RemoveResourceCache(string code, ResourceType type)
    {
        var serviceName = _provider.GetServiceName();
        var key = GetResourceKey(code, serviceName, type);
        await _cache.RemoveAsync(key);
        return true;
    }

    /// <summary>
    /// 获取指定资源的角色缓存键
    /// </summary>
    /// <returns></returns>
    private static string GetResourceRolesKey(long resourceId, string serviceName) => $"Auth:ResourceRoles:{serviceName}:{resourceId}";

    /// <summary>
    /// 获取指定资源的缓存键
    /// </summary>
    /// <returns></returns>
    private static string GetResourceKey(string code, string serviceName, ResourceType type) => $"Auth:Resources:{serviceName}:{type}:{code}";

}
